diff options
| author | joonhoekim <26rote@gmail.com> | 2025-06-23 06:44:34 +0000 |
|---|---|---|
| committer | joonhoekim <26rote@gmail.com> | 2025-06-23 06:44:34 +0000 |
| commit | ebe273ef4564d55f9bf193adc51a9e58211e72e9 (patch) | |
| tree | 30e8c48be41d14751eceb4c24d88c18d03e9102b /app/api/auth/[...nextauth]/saml/provider.ts | |
| parent | abd9f950bbd95b9ad713a26d3fd8a7e0282b7c51 (diff) | |
(김준회 SAML 2.0 SSO 리팩터링, 디버깅 유틸리티 추가, MOCK 처리 추가
Diffstat (limited to 'app/api/auth/[...nextauth]/saml/provider.ts')
| -rw-r--r-- | app/api/auth/[...nextauth]/saml/provider.ts | 159 |
1 files changed, 143 insertions, 16 deletions
diff --git a/app/api/auth/[...nextauth]/saml/provider.ts b/app/api/auth/[...nextauth]/saml/provider.ts index 92099be0..1f891661 100644 --- a/app/api/auth/[...nextauth]/saml/provider.ts +++ b/app/api/auth/[...nextauth]/saml/provider.ts @@ -1,5 +1,9 @@ import CredentialsProvider from "next-auth/providers/credentials" import { getOrCreateSAMLUser, validateSAMLUserData } from '@/lib/users/saml-service' +import { encode } from 'next-auth/jwt' +import type { User } from 'next-auth' +import type { SAMLUser } from './utils' +import { debugLog, debugError, debugSuccess, debugProcess } from '@/lib/debug-utils' interface SAMLProviderOptions { id: string @@ -28,59 +32,80 @@ export function SAMLProvider(options: SAMLProviderOptions) { } }, async authorize(credentials) { + debugLog('🔍 SAMLProvider.authorize called with credentials:', credentials); + try { + debugLog('🔍 Checking credentials.user:', { + hasCredentials: !!credentials, + hasUser: !!credentials?.user, + userType: typeof credentials?.user, + userValue: credentials?.user?.substring?.(0, 100) + '...' + }); + if (!credentials?.user) { - console.error('No user data provided') + debugError('No user data provided in credentials') return null } - console.log('🔐 SAML Provider: Processing user data') + debugProcess('SAML Provider: Processing user data') // 사용자 데이터 파싱 (UTF-8 처리 개선) const userDataString = credentials.user - console.log('🔤 Raw user data string:', userDataString.substring(0, 200) + '...') + debugLog('🔤 Raw user data string:', userDataString.substring(0, 200) + '...') - const userData = JSON.parse(userDataString) + let userData; + try { + userData = JSON.parse(userDataString); + debugSuccess('JSON parsing successful:', userData); + } catch (parseError) { + debugError('JSON parsing failed:', parseError); + debugError('Raw string that failed to parse:', userDataString); + return null; + } // 파싱된 데이터의 UTF-8 확인 - console.log('🔤 Parsed user data UTF-8 check:', { + debugLog('🔤 Parsed user data UTF-8 check:', { name: userData.name, nameLength: userData.name?.length, charCodes: userData.name ? [...userData.name].map(c => c.charCodeAt(0)) : [] }) if (!userData.id || !userData.email) { - console.error('Invalid SAML user data:', userData) + debugError('Invalid SAML user data:', userData) return null } - console.log('✅ SAML Provider: User authenticated successfully', { + debugSuccess('SAML Provider: User authenticated successfully', { id: userData.id, email: userData.email, name: userData.name }) // 🔥 SAML 사용자 데이터 검증 + debugProcess('Validating SAML user data structure...'); const isValidData = await validateSAMLUserData(userData) + debugLog('Validation result:', isValidData); if (!isValidData) { - console.error('Invalid SAML user data structure:', userData) + debugError('Invalid SAML user data structure:', userData) return null } // 🔥 JIT (Just-In-Time) 사용자 생성 또는 조회 - const dbUser = await getOrCreateSAMLUser({ + debugProcess('Creating/getting SAML user from database...'); + const userCreateData = { email: userData.email, name: userData.name, - // companyId: userData.companyId, - // techCompanyId: userData.techCompanyId, - // ! domain = evcp 이면 vendor가 갖는 companyId, techCompanyId는 null companyId: undefined, techCompanyId: undefined, domain: userData.domain - }) + }; + debugLog('User create data:', userCreateData); + + const dbUser = await getOrCreateSAMLUser(userCreateData) + debugLog('Database user result:', dbUser); if (!dbUser) { - console.error('Failed to get or create SAML user') + debugError('Failed to get or create SAML user') return null } @@ -95,10 +120,15 @@ export function SAMLProvider(options: SAMLProviderOptions) { imageUrl: dbUser.imageUrl, // DB의 실제 이미지 URL } - console.log('✅ SAML Provider: Returning user data to NextAuth:', userResult) + debugSuccess('SAML Provider: Returning user data to NextAuth:', userResult) return userResult } catch (error) { - console.error('❌ SAML Provider: Authentication failed', error) + debugError('SAML Provider: Authentication failed', { + error: error instanceof Error ? error.message : String(error), + stack: error instanceof Error ? error.stack : undefined, + errorType: typeof error, + credentials: credentials + }); return null } } @@ -125,4 +155,101 @@ export function validateSAMLOptions(options: SAMLProviderOptions): boolean { return required.every(field => field && field.length > 0) } + +// SAMLProvider의 authorize 함수를 직접 호출하기 위한 헬퍼 +export async function authenticateSAMLUser(userData: SAMLUser) { + debugLog('authenticateSAMLUser called with:', userData); + + try { + // SAMLProvider 대신 직접 로직 실행 (Provider 래퍼 없이) + debugProcess('SAML User Authentication: Processing user data') + + // 사용자 데이터 검증 + if (!userData.id || !userData.email) { + debugError('Invalid SAML user data:', userData) + return null + } + + debugSuccess('SAML User data validated successfully', { + id: userData.id, + email: userData.email, + name: userData.name + }) + + // 🔥 SAML 사용자 데이터 검증 + debugLog('Validating SAML user data structure...'); + const isValidData = await validateSAMLUserData(userData) + debugLog('Validation result:', isValidData); + if (!isValidData) { + debugError('Invalid SAML user data structure:', userData) + return null + } + + // 🔥 JIT (Just-In-Time) 사용자 생성 또는 조회 + debugLog('Creating/getting SAML user from database...'); + const userCreateData = { + email: userData.email, + name: userData.name, + companyId: undefined, + techCompanyId: undefined, + domain: userData.domain + }; + debugLog('User create data:', userCreateData); + + const dbUser = await getOrCreateSAMLUser(userCreateData) + debugLog('Database user result:', dbUser); + + if (!dbUser) { + debugError('Failed to get or create SAML user') + return null + } + + // DB에서 가져온 실제 사용자 정보 반환 + const userResult = { + id: String(dbUser.id), // DB의 실제 ID + name: dbUser.name, // DB의 실제 이름 + email: dbUser.email, // DB의 실제 이메일 + companyId: dbUser.companyId, // DB의 실제 회사 ID + techCompanyId: dbUser.techCompanyId, // DB의 실제 기술회사 ID + domain: dbUser.domain, // DB의 실제 도메인 + imageUrl: dbUser.imageUrl, // DB의 실제 이미지 URL + } + + debugSuccess('SAML User Authentication completed:', userResult) + return userResult; + + } catch (error) { + debugError('authenticateSAMLUser error:', { + error: error instanceof Error ? error.message : String(error), + stack: error instanceof Error ? error.stack : undefined, + userData + }); + return null; + } +} + +// NextAuth JWT 토큰 생성 헬퍼 +export async function createNextAuthToken(user: User): Promise<string> { + const token = { + id: user.id, + email: user.email, + name: user.name, + companyId: user.companyId, + techCompanyId: user.techCompanyId, + domain: user.domain, + imageUrl: user.imageUrl, + iat: Math.floor(Date.now() / 1000), + exp: Math.floor(Date.now() / 1000) + (30 * 24 * 60 * 60) // 30일 + }; + + const secret = process.env.NEXTAUTH_SECRET!; + return await encode({ token, secret }); +} + +// NextAuth 세션 쿠키 이름 가져오기 +export function getSessionCookieName(): string { + return process.env.NODE_ENV === 'production' + ? '__Secure-next-auth.session-token' + : 'next-auth.session-token'; +}
\ No newline at end of file |
